//
//  main.cpp
//  进阶
//
//  Created by 沈勇 on 2020/1/5.
//  Copyright © 2020 沈勇. All rights reserved.
//

#include <iostream>
using namespace std;
#include <opencv2/opencv.hpp>
using namespace cv;
// 第一题：滤波
void imgBlur(){
    Mat img = imread("./lena.jpg");
    imshow("Lena", img);
    cvtColor(img, img, COLOR_BGR2GRAY);
    //高斯滤波，正态分布滤波，边缘对中心的影响较小，对高斯噪声有效
    Mat gsImg;
    GaussianBlur(img, gsImg, Size(5,5),1,1);
    imshow("GsBlur", gsImg);
    //二次高斯滤波，对噪声进一步平滑过滤
    Mat gsImg2;
    GaussianBlur(gsImg, gsImg2, Size(5,5),1,1);
    imshow("GsBlur2", gsImg2);
    //中值滤波，能很好的去掉椒盐噪声，去噪效果好
    Mat mdImg;
    medianBlur(img, mdImg, 5);
    imshow("MdBlur", mdImg);
    //均值滤波,图像被平滑模糊，降噪效果一般
    Mat avImg;
    blur(img, avImg, Size(5,5));
    imshow("AvImg", avImg);

    //数学心态学滤波 先开（腐蚀再膨胀）后闭（膨胀再腐蚀），能有效的降噪
    Mat mathImg_open;
    Mat mathImg;
    Mat element = getStructuringElement(MORPH_RECT, Size(3, 3) );
    morphologyEx(img, mathImg_open,MORPH_OPEN,element);
    morphologyEx(mathImg_open, mathImg,MORPH_CLOSE,element);
    imshow("Math", mathImg);

}
// 第二题：边缘检测
void edgeDetective(){
    Mat img = imread("./lena.jpg");
    imshow("Lena", img);
    cvtColor(img, img, COLOR_BGR2GRAY);
    // Sobel算子边缘检测，具有梯度赋值和方向的边缘检测，边缘比较粗
    Mat sobel;
    Sobel(img, sobel, CV_8U, 0, 1);
    imshow("Sobel", sobel);
    // Laplace算子边缘检测，对噪声敏感，降噪的同时不能很好的保留细节
    Mat lap;
    Laplacian(img, lap, CV_8U);
    imshow("Lap",lap);
    // Canny算子边缘检测效果在这三种之中最好，能很好的检测图像边缘，也能很好的去除噪声
    Mat canny;
    Canny(img, canny, 100,250);
    imshow("Canny", canny);
}
// 第三题：灰度直方图和大津算法
void otsu(){
    Mat img = imread("./pic2.png");
    Mat gray;
    cvtColor(img, gray, COLOR_BGR2GRAY);
    //中值滤波去掉椒盐噪声
    GaussianBlur(gray, gray,Size(5,5), 3);
    //计算直方图图像
    const int Gray=256;
    int histogram[Gray] = {0};
    int rows = img.rows;
    int cols = img.cols;
    for(int row=0;row<rows;row++){
        for(int col = 0;col<cols;col++){
            int index = int(img.at<uchar>(row,col));
            histogram[index]++;
        }
    }
    //大津算法实现
    //总像素值
    int sum = rows*cols;
    double pixPro[Gray] = {0};
    // 计算每个灰度占像素的比例
    for(int i=0;i<Gray;i++){
        pixPro[i] = histogram[i]*1.0 / sum;
    }
    double w0,w1,u0,u1,temp,temp1,temp2;
    double max=-1.0;
    int bestThresh=0;
    // 遍历所有灰度值计算类间方差最大的最大灰度阈值
    for(int i=0;i<Gray;i++){
        w0=w1=u0=u1=temp=temp1=temp2=0;
        for(int j=0;j<Gray;j++){
            // 背景
            if(j<i){
                w0+=pixPro[j];
                temp1+=j*pixPro[j];
            }else{
                //前景
                w1+=pixPro[j];
                temp2+=j*pixPro[j];
            }
        }
        u0=temp1/w0; //背景平均灰度
        u1=temp2/w1; //前景平均灰度
        temp=w0*w1*(u0-u1)*(u0-u1); //计算类间方差
        if(temp>max){
            bestThresh=i; //记录最大类间方差和最佳阈值
            max=temp;
        }
    }
    Mat myOtsu;
    Mat cvOtsu;
    // 我的大津
    threshold(gray, myOtsu, bestThresh, 255, THRESH_BINARY);
    // opencv的大津
    threshold(gray, cvOtsu, 0, 255, THRESH_OTSU);
    imshow("img", img);
    imshow("myOtsu", myOtsu);
    imshow("cvOtsu", cvOtsu);
    // 结果有细微差异。。。不知道是哪里的问题
}
// 第四题：计算大米数量跟面积（只计算了面积和数量统计）
void calRice(){
    Mat riceImg = imread("./rice.png");
    Mat gray,bw;
    cvtColor(riceImg, gray, COLOR_BGR2GRAY);
    Mat element = getStructuringElement(MORPH_RECT, Size(3, 3) );
    adaptiveThreshold(gray, bw, 0xff, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY,25,0);
    morphologyEx(bw, bw,MORPH_OPEN,element);
    imshow("gray", gray);
    imshow("bw", bw);
    Mat seg = bw.clone();
    vector<vector<Point>> cnts;
    findContours(seg, cnts, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    
    int total=0;
    vector<Point> c;
    Rect rect;
    RotatedRect minRect;
    double area;
    string areaStr;
    int w,h;
    for(int i=0;i<cnts.size();i++){
        //计数器加1
        total++;
        c=cnts[i];
        //计算每一个contour面积
        area=contourArea(c);
        if(area<10){
            continue;
        }
        //生成原始图上的矩阵
        minRect = minAreaRect({c});
        Point2f vertices[4];
        minRect.points(vertices);//获取矩形的四个点
        for (int i = 0; i < 4; i++)
            line(riceImg, vertices[i], vertices[(i+1)%4], Scalar(0,0,0xff));
//        rect = boundingRect(c);
        rect = minRect.boundingRect();
        w=rect.width;
        h=rect.height;
//        cout << minRect.boundingRect() << "w" << minRect.boundingRect().width << "h"<< minRect.boundingRect().height <<endl;
//        cout << rect << "w" << rect.width << "h" << rect.height << endl;
//        rectangle(riceImg, rect, Scalar(0,0,0xff));
        //打上面积描述
        stringstream s;
        s << area;
        s >> areaStr;
        putText(riceImg, areaStr, Point(rect.x,rect.y), CV_HAL_DFT_IS_CONTINUOUS, 0.5, Scalar(0,0,0xff));
//        cout <<  "第" << total << "个米粒长度为：" << sqrt(pow(rect.height,2)+pow(rect.width,2)) <<"；面积为" << area << endl;
        cout <<  "第" << total << "个米粒长度为：" << max(w,h) << "宽度为"<< min(w,h)<<"；面积为" << area << endl;
    }
    cout << "米粒数量为" << total << endl;
    imshow("rice", riceImg);
}
int main(int argc, const char * argv[]) {
    //第一题
    imgBlur();
    //第二题
    edgeDetective();
    //第三题
    otsu();
    //第四题
    calRice();
    
    waitKey(0);
    destroyAllWindows();
    return 0;
}
